package com.hello.sandbox.core.system.am;

import static android.content.pm.PackageManager.GET_ACTIVITIES;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import black.android.app.BRActivityManagerNative;
import black.android.app.BRIActivityManager;
import black.com.android.internal.BRRstyleable;
import com.hello.sandbox.SandBoxCore;
import com.hello.sandbox.core.system.BProcessManagerService;
import com.hello.sandbox.core.system.ProcessRecord;
import com.hello.sandbox.core.system.pm.BPackageManagerService;
import com.hello.sandbox.core.system.pm.PackageManagerCompat;
import com.hello.sandbox.proxy.ProxyActivity;
import com.hello.sandbox.proxy.ProxyManifest;
import com.hello.sandbox.proxy.record.ProxyActivityRecord;
import com.hello.sandbox.utils.ComponentUtils;
import com.hello.sandbox.utils.Slog;
import com.hello.sandbox.utils.compat.ActivityManagerCompat;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/** Created by Milk on 4/5/21. * ∧＿∧ (`･ω･∥ 丶　つ０ しーＪ 此处无Bug */
public class ActivityStack {
  public static final String TAG = "ActivityStack";

  private final ActivityManager mAms;
  private final Map<Integer, TaskRecord> mTasks = new LinkedHashMap<>();
  private final Set<ActivityRecord> mLaunchingActivities = new HashSet<>();

  public static final int LAUNCH_TIME_OUT = 0;
  private final Handler mHandler =
      new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
          switch (msg.what) {
            case LAUNCH_TIME_OUT:
              ActivityRecord record = (ActivityRecord) msg.obj;
              if (record != null) {
                mLaunchingActivities.remove(record);
              }
              break;
            default:
              break;
          }
        }
      };

  public ActivityStack() {
    mAms = (ActivityManager) SandBoxCore.getContext().getSystemService(Context.ACTIVITY_SERVICE);
  }

  public boolean containsFlag(Intent intent, int flag) {
    return (intent.getFlags() & flag) != 0;
  }

  public int startActivitiesLocked(
      int userId, Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options) {
    if (intents == null) {
      throw new NullPointerException("intents is null");
    }
    if (resolvedTypes == null) {
      throw new NullPointerException("resolvedTypes is null");
    }
    if (intents.length != resolvedTypes.length) {
      throw new IllegalArgumentException("intents are length different than resolvedTypes");
    }
    for (int i = 0; i < intents.length; i++) {
      startActivityLocked(userId, intents[i], resolvedTypes[i], resultTo, null, -1, 0, options);
    }
    return 0;
  }

  public int startActivityLocked(
      int userId,
      Intent intent,
      String resolvedType,
      IBinder resultTo,
      String resultWho,
      int requestCode,
      int flags,
      Bundle options) {
    synchronized (mTasks) {
      synchronizeTasks();
    }

    ResolveInfo resolveInfo =
        BPackageManagerService.get().resolveActivity(intent, GET_ACTIVITIES, resolvedType, userId);
    if (resolveInfo == null || resolveInfo.activityInfo == null) {
      return 0;
    }
    Log.d(TAG, "startActivityLocked : " + resolveInfo.activityInfo);
    ActivityInfo activityInfo = resolveInfo.activityInfo;

    ActivityRecord sourceRecord = findActivityRecordByToken(userId, resultTo);
    if (sourceRecord == null) {
      resultTo = null;
    }
    TaskRecord sourceTask = null;
    if (sourceRecord != null) {
      sourceTask = sourceRecord.task;
    }

    String taskAffinity = ComponentUtils.getTaskAffinity(activityInfo);

    int launchModeFlags = 0;
    boolean singleTop =
        containsFlag(intent, Intent.FLAG_ACTIVITY_SINGLE_TOP)
            || activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;
    boolean newTask = containsFlag(intent, Intent.FLAG_ACTIVITY_NEW_TASK);
    boolean clearTop = containsFlag(intent, Intent.FLAG_ACTIVITY_CLEAR_TOP);
    boolean clearTask = containsFlag(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);

    TaskRecord taskRecord = null;
    switch (activityInfo.launchMode) {
      case ActivityInfo.LAUNCH_SINGLE_TOP:
      case ActivityInfo.LAUNCH_MULTIPLE:
      case ActivityInfo.LAUNCH_SINGLE_TASK:
        taskRecord = findTaskRecordByTaskAffinityLocked(userId, taskAffinity);
        if (taskRecord == null && !newTask) {
          taskRecord = sourceTask;
        }
        break;
      case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
        taskRecord = findTaskRecordByTaskAffinityLocked(userId, taskAffinity);
        break;
    }

    // 如果还没有task则新启动一个task
    if (taskRecord == null || taskRecord.needNewTask()) {
      return startActivityInNewTaskLocked(userId, intent, activityInfo, resultTo, launchModeFlags);
    }
    // 移至前台
    mAms.moveTaskToFront(taskRecord.id, 0);

    boolean notStartToFront = false;
    if (clearTop || singleTop || clearTask) {
      notStartToFront = true;
    }

    boolean startTaskToFront =
        !notStartToFront
            && ComponentUtils.intentFilterEquals(taskRecord.rootIntent, intent)
            && taskRecord.rootIntent.getFlags() == intent.getFlags();

    if (startTaskToFront) return 0;

    ActivityRecord topActivityRecord = taskRecord.getTopActivityRecord();
    ActivityRecord targetActivityRecord =
        findActivityRecordByComponentName(userId, ComponentUtils.toComponentName(activityInfo));
    ActivityRecord newIntentRecord = null;
    boolean ignore = false;

    if (clearTop) {
      if (targetActivityRecord != null) {
        // 目标栈上面所有activity出栈
        synchronized (targetActivityRecord.task.activities) {
          for (int i = targetActivityRecord.task.activities.size() - 1; i >= 0; i--) {
            ActivityRecord next = targetActivityRecord.task.activities.get(i);
            if (next != targetActivityRecord) {
              next.finished = true;
              Log.d(TAG, "makerFinish: " + next.component.toString());
            } else {
              if (singleTop) {
                newIntentRecord = targetActivityRecord;
              } else {
                // clearTop并且不是singleTop，目标也finish，重建。
                targetActivityRecord.finished = true;
              }
              break;
            }
          }
        }
      }
    }

    if (singleTop && !clearTop) {
      if (ComponentUtils.intentFilterEquals(topActivityRecord.intent, intent)) {
        newIntentRecord = topActivityRecord;
      } else {
        synchronized (mLaunchingActivities) {
          for (ActivityRecord launchingActivity : mLaunchingActivities) {
            if (!launchingActivity.finished
                && launchingActivity.component.equals(intent.getComponent())) {
              // todo update onNewIntent from intent
              ignore = true;
            }
          }
        }
      }
    }

    if (activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK && !clearTop) {
      if (ComponentUtils.intentFilterEquals(topActivityRecord.intent, intent)) {
        newIntentRecord = topActivityRecord;
      } else {
        ActivityRecord record =
            findActivityRecordByComponentName(userId, ComponentUtils.toComponentName(activityInfo));
        if (record != null) {
          // 需要调用目标onNewIntent
          newIntentRecord = record;
          // 目标栈上面所有activity出栈
          synchronized (taskRecord.activities) {
            for (int i = taskRecord.activities.size() - 1; i >= 0; i--) {
              ActivityRecord next = taskRecord.activities.get(i);
              if (next != record) {
                next.finished = true;
              } else {
                break;
              }
            }
          }
        }
      }
    }

    if (activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
      newIntentRecord = topActivityRecord;
    }

    // clearTask finish All
    if (clearTask && newTask) {
      for (ActivityRecord activity : taskRecord.activities) {
        activity.finished = true;
      }
    }

    finishAllActivity(userId);

    if (newIntentRecord != null) {
      // 通知onNewIntent
      deliverNewIntentLocked(newIntentRecord, intent);
      return 0;
    } else if (ignore) {
      return 0;
    }

    if (resultTo == null) {
      ActivityRecord top = taskRecord.getTopActivityRecord();
      if (top != null) {
        resultTo = top.token;
      }
    } else if (sourceTask != null) {
      ActivityRecord top = sourceTask.getTopActivityRecord();
      if (top != null) {
        resultTo = top.token;
      }
    }
    return startActivityInSourceTask(
        intent,
        resolvedType,
        resultTo,
        resultWho,
        requestCode,
        flags,
        options,
        userId,
        topActivityRecord,
        activityInfo,
        launchModeFlags);
  }

  private void deliverNewIntentLocked(ActivityRecord activityRecord, Intent intent) {
    try {
      activityRecord.processRecord.bActivityThread.handleNewIntent(activityRecord.token, intent);
    } catch (RemoteException e) {
      e.printStackTrace();
    }
  }

  private Intent startActivityProcess(
      int userId, Intent intent, ActivityInfo info, ActivityRecord record) {
    ProxyActivityRecord stubRecord = new ProxyActivityRecord(userId, info, intent, record);
    ProcessRecord targetApp =
        BProcessManagerService.get()
            .startProcessLocked(
                info.packageName, info.processName, userId, -1, Binder.getCallingPid());
    if (targetApp == null) {
      throw new RuntimeException("Unable to create process, name:" + info.name);
    }
    return getStartStubActivityIntentInner(intent, targetApp.bpid, userId, stubRecord, info);
  }

  private int startActivityInNewTaskLocked(
      int userId, Intent intent, ActivityInfo activityInfo, IBinder resultTo, int launchMode) {
    ActivityRecord record = newActivityRecord(intent, activityInfo, resultTo, userId);
    Intent shadow = startActivityProcess(userId, intent, activityInfo, record);

    shadow.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    shadow.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
    shadow.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    shadow.addFlags(launchMode);

    SandBoxCore.getContext().startActivity(shadow);
    return 0;
  }

  private int startActivityInSourceTask(
      Intent intent,
      String resolvedType,
      IBinder resultTo,
      String resultWho,
      int requestCode,
      int flags,
      Bundle options,
      int userId,
      ActivityRecord sourceRecord,
      ActivityInfo activityInfo,
      int launchMode) {
    ActivityRecord selfRecord = newActivityRecord(intent, activityInfo, resultTo, userId);
    Intent shadow = startActivityProcess(userId, intent, activityInfo, selfRecord);
    shadow.setAction(UUID.randomUUID().toString());
    shadow.addFlags(launchMode);
    if (resultTo == null) {
      shadow.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    return realStartActivityLocked(
        sourceRecord.processRecord.appThread,
        shadow,
        resolvedType,
        resultTo,
        resultWho,
        requestCode,
        flags,
        options);
  }

  private int realStartActivityLocked(
      IInterface appThread,
      Intent intent,
      String resolvedType,
      IBinder resultTo,
      String resultWho,
      int requestCode,
      int flags,
      Bundle options) {
    try {
      flags &= ~ActivityManagerCompat.START_FLAG_DEBUG;
      flags &= ~ActivityManagerCompat.START_FLAG_NATIVE_DEBUGGING;
      flags &= ~ActivityManagerCompat.START_FLAG_TRACK_ALLOCATION;

      BRIActivityManager.get(BRActivityManagerNative.get().getDefault())
          .startActivity(
              appThread,
              SandBoxCore.getHostPkg(),
              intent,
              resolvedType,
              resultTo,
              resultWho,
              requestCode,
              flags,
              null,
              options);
    } catch (Throwable e) {
      e.printStackTrace();
    }
    return 0;
  }

  private ActivityRecord getTopActivityRecord() {
    synchronized (mTasks) {
      synchronizeTasks();
    }
    List<TaskRecord> tasks = new LinkedList<>(mTasks.values());
    if (tasks.isEmpty()) return null;
    return tasks.get(tasks.size() - 1).getTopActivityRecord();
  }

  private Intent getStartStubActivityIntentInner(
      Intent intent, int vpid, int userId, ProxyActivityRecord target, ActivityInfo activityInfo) {
    Intent shadow = new Intent();
    TypedArray typedArray = null;
    try {
      Resources resources =
          PackageManagerCompat.getResources(SandBoxCore.getContext(), activityInfo.applicationInfo);
      int id;
      if (activityInfo.theme != 0) {
        id = activityInfo.theme;
      } else {
        id = activityInfo.applicationInfo.theme;
      }
      assert resources != null;
      typedArray = resources.newTheme().obtainStyledAttributes(id, BRRstyleable.get().Window());
      boolean windowIsTranslucent =
          typedArray.getBoolean(BRRstyleable.get().Window_windowIsTranslucent(), false);
      if (windowIsTranslucent) {
        shadow.setComponent(
            new ComponentName(
                SandBoxCore.getHostPkg(), ProxyManifest.TransparentProxyActivity(vpid)));
      } else {
        shadow.setComponent(
            new ComponentName(SandBoxCore.getHostPkg(), ProxyManifest.getProxyActivity(vpid)));
      }
      Slog.d(TAG, activityInfo + ", windowIsTranslucent: " + windowIsTranslucent);
    } catch (Throwable e) {
      e.printStackTrace();
      shadow.setComponent(
          new ComponentName(SandBoxCore.getHostPkg(), ProxyManifest.getProxyActivity(vpid)));
    } finally {
      if (typedArray != null) {
        typedArray.recycle();
      }
    }
    ProxyActivityRecord.saveStub(
        shadow, intent, target.mActivityInfo, target.mActivityRecord, target.mUserId);
    return shadow;
  }

  private void finishAllActivity(int userId) {
    for (TaskRecord task : mTasks.values()) {
      for (ActivityRecord activity : task.activities) {
        if (activity.userId == userId) {
          if (activity.finished) {
            try {
              activity.processRecord.bActivityThread.finishActivity(activity.token);
            } catch (RemoteException ignored) {
            }
          }
        }
      }
    }
  }

  ActivityRecord newActivityRecord(Intent intent, ActivityInfo info, IBinder resultTo, int userId) {
    ActivityRecord targetRecord = ActivityRecord.create(intent, info, resultTo, userId);
    synchronized (mLaunchingActivities) {
      mLaunchingActivities.add(targetRecord);
      Message obtain = Message.obtain(mHandler, LAUNCH_TIME_OUT, targetRecord);
      mHandler.sendMessageDelayed(obtain, 2000);
    }
    return targetRecord;
  }

  private ActivityRecord findActivityRecordByComponentName(
      int userId, ComponentName componentName) {
    ActivityRecord record = null;
    for (TaskRecord next : mTasks.values()) {
      if (userId == next.userId) {
        for (ActivityRecord activity : next.activities) {
          if (activity.component.equals(componentName)) {
            record = activity;
            break;
          }
        }
      }
    }
    return record;
  }

  private ActivityRecord findActivityRecordByToken(int userId, IBinder token) {
    ActivityRecord record = null;
    if (token != null) {
      for (TaskRecord next : mTasks.values()) {
        if (userId == next.userId) {
          for (ActivityRecord activity : next.activities) {
            if (activity.token == token) {
              record = activity;
              break;
            }
          }
        }
      }
    }
    return record;
  }

  private TaskRecord findTaskRecordByTaskAffinityLocked(int userId, String taskAffinity) {
    synchronized (mTasks) {
      for (TaskRecord next : mTasks.values()) {
        if (userId == next.userId && next.taskAffinity.equals(taskAffinity)) return next;
      }
      return null;
    }
  }

  private TaskRecord findTaskRecordByTokenLocked(int userId, IBinder token) {
    synchronized (mTasks) {
      for (TaskRecord next : mTasks.values()) {
        if (userId == next.userId) {
          for (ActivityRecord activity : next.activities) {
            if (activity.token == token) {
              return next;
            }
          }
        }
      }
      return null;
    }
  }

  public void onActivityCreated(
      ProcessRecord processRecord, int taskId, IBinder token, ActivityRecord record) {
    synchronized (mLaunchingActivities) {
      mLaunchingActivities.remove(record);
      mHandler.removeMessages(LAUNCH_TIME_OUT, record);
    }
    synchronized (mTasks) {
      synchronizeTasks();
      TaskRecord taskRecord = mTasks.get(taskId);
      if (taskRecord == null) {
        taskRecord =
            new TaskRecord(taskId, record.userId, ComponentUtils.getTaskAffinity(record.info));
        taskRecord.rootIntent = record.intent;
        mTasks.put(taskId, taskRecord);
      }
      record.token = token;
      record.processRecord = processRecord;
      record.task = taskRecord;
      taskRecord.addTopActivity(record);
      Log.d(TAG, "onActivityCreated : " + record.component.toString());
    }
  }

  public void onActivityResumed(int userId, IBinder token) {
    synchronized (mTasks) {
      synchronizeTasks();
      ActivityRecord activityRecord = findActivityRecordByToken(userId, token);
      if (activityRecord == null) {
        return;
      }
      Log.d(TAG, "onActivityResumed : " + activityRecord.component.toString());
      activityRecord.task.removeActivity(activityRecord);
      activityRecord.task.addTopActivity(activityRecord);
    }
  }

  public void onActivityDestroyed(int userId, IBinder token) {
    synchronized (mTasks) {
      synchronizeTasks();
      ActivityRecord activityRecord = findActivityRecordByToken(userId, token);
      if (activityRecord == null) {
        return;
      }
      activityRecord.finished = true;
      Log.d(TAG, "onActivityDestroyed : " + activityRecord.component.toString());
      activityRecord.task.removeActivity(activityRecord);
    }
  }

  public void onFinishActivity(int userId, IBinder token) {
    synchronized (mTasks) {
      synchronizeTasks();
      ActivityRecord activityRecord = findActivityRecordByToken(userId, token);
      if (activityRecord == null) {
        return;
      }
      activityRecord.finished = true;
      Log.d(TAG, "onFinishActivity : " + activityRecord.component.toString());
    }
  }

  public String getCallingPackage(IBinder token, int userId) {
    synchronized (mTasks) {
      synchronizeTasks();
      ActivityRecord activityRecordByToken = findActivityRecordByToken(userId, token);
      if (activityRecordByToken != null) {
        ActivityRecord resultTo = findActivityRecordByToken(userId, activityRecordByToken.resultTo);
        if (resultTo != null) {
          return resultTo.info.packageName;
        }
      }
      return SandBoxCore.getHostPkg();
    }
  }

  public ComponentName getCallingActivity(IBinder token, int userId) {
    synchronized (mTasks) {
      synchronizeTasks();
      ActivityRecord activityRecordByToken = findActivityRecordByToken(userId, token);
      if (activityRecordByToken != null) {
        ActivityRecord resultTo = findActivityRecordByToken(userId, activityRecordByToken.resultTo);
        if (resultTo != null) {
          return resultTo.component;
        }
      }
      return new ComponentName(SandBoxCore.getHostPkg(), ProxyActivity.P0.class.getName());
    }
  }

  private void synchronizeTasks() {
    List<ActivityManager.RecentTaskInfo> recentTasks = mAms.getRecentTasks(100, 0);
    Map<Integer, TaskRecord> newTacks = new LinkedHashMap<>();
    for (int i = recentTasks.size() - 1; i >= 0; i--) {
      ActivityManager.RecentTaskInfo next = recentTasks.get(i);
      TaskRecord taskRecord = mTasks.get(next.id);
      if (taskRecord == null) continue;
      newTacks.put(next.id, taskRecord);
    }
    mTasks.clear();
    mTasks.putAll(newTacks);
  }
}
